/**
 * Copyright (C) 2013 Guangzhou Tronlong Electronic Technology Co., Ltd. - www.tronlong.com
 * 
 * @file mqtt_client.c
 * 
 * @brief Example application main file.
 * This application publish or subscribe message use the MQTT protocol
 * 
 * @author Tronlong <support@tronlong.com>
 * 
 * @version V1.0
 * 
 * @date 2022-2-15
 **/

#include <stdio.h>
#include <stdbool.h>
#include <getopt.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <libgen.h>
#include <stdint.h>
#include <signal.h>
#include <math.h>
#include <mosquitto.h>

const char * const VERSION = "1.0";

/* Short option names */
static const char g_shortopts [] = ":h:p:t:m:M:q:v";

/* Option names */
static const struct option g_longopts [] = {
    { "host",        required_argument,      NULL,        'h' },
    { "port",        required_argument,      NULL,        'p' },
    { "topic",       required_argument,      NULL,        't' },
    { "message",     required_argument,      NULL,        'm' },
    { "mode",        required_argument,      NULL,        'M' },
    { "quality",     required_argument,      NULL,        'q' },
    { "version",     no_argument,            NULL,        'v' },
    { "help",        no_argument,            NULL,         0  },
    { 0, 0, 0, 0 }
};

enum Mode{
    MQTT_PUBLISH,
    MQTT_SUBSCRIBE,
    MQTT_INVALID
};

struct _Params {
    char host[64];
    char topic[64];
    char message[128];
    int  port;
    int  quality;
    enum Mode mode;
};

static const int keepalive = 600;
static int is_connect = false;

/* Exit flag */
volatile bool g_quit = false;

struct mosquitto *mosq = NULL;

static void usage(char **argv)
{
    fprintf(stdout,
            "Usage: %s [options]\n\n"
            "Options:\n"
            " -h | --host          The hostname or ip address of the broker to connect to.\n"
            " -p | --port          The network port to connect to. Usually 1883.\n"
            " -t | --topic         MQTT topic to publish to.\n"
            " -m | --message       Message payload to send.\n"
            " -M | --mode          MQTT mode [publish|subscribe]\n"
            " -q | --quality       quality of service level to use for all messages. Defaults to 0. value: [0, 1, 2]\n"
            " -v | --version       Display version information\n"
            " --help               Show help content\n\n"
            "Example:\n"
            "  # ./%s -h 192.168.0.115 -p 1883 -t test -m 'www.tronlong.com'\n"
            "", basename(argv[0]),
                basename(argv[0]));
}

static bool parse_parameter(struct _Params *params, int argc, char **argv)
{
    int opt;

    /* Set default value */
    params->port = 0;
    params->quality = 0;
    params->mode = MQTT_INVALID;
    memset(params->topic, 0, sizeof(params->topic));
    memset(params->message, 0, sizeof(params->message));

	while ((opt = getopt_long(argc, argv, g_shortopts, g_longopts, NULL)) != -1) {
		switch (opt) {
		case 'h':
            strcpy(params->host, optarg);
			break;

		case 'p':
            params->port = atoi(optarg);
			break;

		case 't':
            strcpy(params->topic, optarg);
			break;

		case 'm':
            strcpy(params->message, optarg);
			break;
        /* mode */
		case 'M':
            if (strcmp(optarg, "publish") == 0) {
                params->mode = MQTT_PUBLISH;
            } else if (strcmp(optarg, "subscribe") == 0) {
                params->mode = MQTT_SUBSCRIBE;
            }
			break;

		case 'q':
            params->quality = atoi(optarg);
			break;

		case 'v':
			printf("version : %s\n", VERSION);
			exit(0);
        /* help */
        case 0:
            usage(argv);
            exit(0);
            break;

		default:
			fprintf(stderr, "Unknown option %c\n", optopt);
			break;
		}
	}

    if (strlen(params->host) == 0 || params->port == 0) {
        return false;
    }

    if (params->mode == MQTT_PUBLISH) {
        if (strlen(params->topic) == 0 || strlen(params->message) == 0) {
            return false;
        }
    } else if(params->mode == MQTT_SUBSCRIBE) {
        if (strlen(params->topic) == 0) {
            return false;
        }
    } else {
        return false;
    }

    return true;
}

void mqtt_connect_callback(struct mosquitto *mosq, void *obj, int rc)
{
    struct _Params *userdata = (struct _Params *)obj;

    if (rc == 0) {
        is_connect = true;
        printf("Connect mqtt broker success (%s:%d).\n", userdata->host, userdata->port);
        
        /* Subscribe topic */
        if (userdata->mode == MQTT_SUBSCRIBE) {
            mosquitto_subscribe(mosq, NULL, userdata->topic, userdata->quality);
        }
    }
}

void mqtt_disconnect_callback(struct mosquitto *mosq, void *obj, int result)
{
    struct _Params *userdata = (struct _Params *)obj;

    if (userdata->mode == MQTT_SUBSCRIBE) {
        printf("disconnect mqtt broker server\n");
    }
}

void mqtt_publish_callback(struct mosquitto *mosq, void *obj, int mid)
{
    g_quit = true;
    printf("send message success.\n");
    mosquitto_disconnect(mosq);
}

void mqtt_message_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *message)
{
    printf("recv message: %s\n", (char *)message->payload);
}

void sig_handle(int arg)
{
    g_quit = true;
    mosquitto_disconnect(mosq);
}

int main(int argc, char **argv)
{
    struct _Params params;
    int ret = -1;
    int status = 0;

    memset(&params, 0, sizeof(params));
    if (parse_parameter(&params, argc, argv) == false) {
        printf("Please try --help to see usage.\n");
        exit(-1);
    }

    /* Ctrl+c handler */
    signal(SIGINT, sig_handle);

    /* Must be called before any other mosquitto functions. */
    ret = mosquitto_lib_init();
    if (ret != MOSQ_ERR_SUCCESS) {
        printf("mosquitto lib init failed!\n");
        exit(-1);
    }

    mosq = mosquitto_new("client1", true, &params);
    if (mosq == NULL) {
        printf("Create new mosquitto client instance failed!\n");
        exit(-1);
    }

    /* Set the connect callback. */
	mosquitto_connect_callback_set(mosq, mqtt_connect_callback);
    /* Set the disconnect callback. */
	mosquitto_disconnect_callback_set(mosq, mqtt_disconnect_callback);
    /* Set the publish callback.  This is called when a message initiated with mosquitto_publish has been sent to the broker successfully. */
	mosquitto_publish_callback_set(mosq, mqtt_publish_callback);
    /* Set the message callback.  This is called when a message is received from the broker. */
    mosquitto_message_callback_set(mosq, mqtt_message_callback);

    /* Connect to an MQTT broker. */
    ret = mosquitto_connect(mosq, params.host, params.port, keepalive);
    if (ret != MOSQ_ERR_SUCCESS) {
        printf("mosquitto connect failed!\n");
        status = -1;
        goto error;
    }

    /* publish */
    if (params.mode == MQTT_PUBLISH) {
        mosquitto_loop_start(mosq);

        while (!g_quit) {
            if (is_connect == false) {
                usleep(100000);
                continue;
            }

            ret = mosquitto_publish(mosq, NULL, params.topic, strlen(params.message), params.message, 0, false);
            if (ret) {
                printf("Error: Publish returned %d, disconnecting.\n", ret);
                mosquitto_disconnect(mosq);
            }

            break;
        }
        
        mosquitto_loop_stop(mosq, false);
    } else if (params.mode == MQTT_SUBSCRIBE) {
        /* subscribe */
        mosquitto_loop_forever(mosq, -1, 1);
    }

error:
    mosquitto_destroy(mosq);
    mosquitto_lib_cleanup();

    return status;
}